//
//  Drumset.m
//  rockband-drums-wii
//
//  Created by Sid Stamm on 8/6/09.
// rockband-drums-wii: enables software synth for Wii-compatible RockBand2 drums
// Copyright (C) 2009  Sid Stamm <sidstamm@gmail.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.


#import "Drumset.h"

#include <mach/mach.h>
#include <mach/mach_error.h>
#include <IOKit/IOKitLib.h>
#include <IOKit/IOCFPlugIn.h>
#include <IOKit/hid/IOHIDLib.h>
#include <IOKit/hid/IOHIDUsageTables.h>
#include <IOKit/usb/USB.h>
#include <CoreFoundation/CoreFoundation.h>
#include <Carbon/Carbon.h>


/**
 * Fetches the HID cookies from the device given: looking specifically for the 
 * usages pertaining to the drums.
 */
static cookie_struct_t 
getHIDCookies(IOHIDDeviceInterface122 **handle)
{
    cookie_struct_t cookies = (cookie_struct_t)memset(malloc(sizeof(*cookies)), 0, sizeof(*cookies));
    CFTypeRef               object;
    long                    number;
    IOHIDElementCookie      cookie;
    long                    usage;
    long                    usagePage;
    CFArrayRef              elements;
    CFDictionaryRef         element;
    IOReturn                success;
    
    if(!handle || !(*handle)) return cookies;
    
    // Copy all elements, since we're grabbing most of the elements
    // for this device anyway, and thus, it's faster to iterate them
    // ourselves. When grabbing only one or two elements, a matching
    // dictionary should be passed in here instead of NULL.
    success = (*handle)->copyMatchingElements(handle, NULL, &elements);
    
    printf("LOOKING FOR ELEMENTS.\n");
    if (success == kIOReturnSuccess) {
        CFIndex i;
        printf("ITERATING...\n");
        for (i=0; i<CFArrayGetCount(elements); i++)    {
            element = (CFDictionaryRef)CFArrayGetValueAtIndex(elements, i);
            //printf("GOT ELEMENT.\n");
            
            //Get cookie
            object = (CFDictionaryGetValue(element, CFSTR(kIOHIDElementCookieKey)));
            if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID())
                continue;
            if(!CFNumberGetValue((CFNumberRef) object, kCFNumberLongType, &number))
                continue;
            cookie = (IOHIDElementCookie) number;
            printf("ELT >> 0x%x ", cookie);
            
            //Get usage
            object = CFDictionaryGetValue(element, CFSTR(kIOHIDElementUsageKey));
            if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID())
                continue;
            if (!CFNumberGetValue((CFNumberRef) object, kCFNumberLongType, &number))
                continue;
            usage = number;
            printf("usage:0x%x ", usage);
            
            //Get usage page
            object = CFDictionaryGetValue(element,CFSTR(kIOHIDElementUsagePageKey));
            if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID())
                continue;
            if (!CFNumberGetValue((CFNumberRef) object, kCFNumberLongType, &number))
                continue;
            usagePage = number;
            printf("page:0x%x \n", usagePage);
            
            // HOW DO WE FIGURE OUT THE USAGE AND PAGE DATA?????
            // arrows (none = 0x1000, up = 0x0000, right = 0x0010, down = 0x0100, left = 0x0110)
            if (usage == 0x39 && usagePage == 0x1)
                cookies->gXAxisCookie = cookie;
            //Check for buttons
            else if (usage == 0x3 && usagePage == 0x9)
                cookies->gRedDrumCookie = cookie;
            else if (usage == 0x4 && usagePage == 0x9)
                cookies->gYellowDrumCookie = cookie;
            else if (usage == 0x1 && usagePage == 0x9)
                cookies->gBlueDrumCookie = cookie;
            else if (usage == 0x2 && usagePage == 0x9)
                cookies->gGreenDrumCookie = cookie;
            else if (usage == 0x5 && usagePage == 0x9)
                cookies->gPedalCookie = cookie;
        }
        printf("DONE.\n");
    } else {
        printf("copyMatchingElements failed with error %d\n", success);
    }
    
    return cookies;
}



/** 
 * This callback is invoked when a drum is physically hit.
 */
void 
DrumInternalIOHIDCallbackFunction(void * target, IOReturn result, 
                                  void * refcon, void * sender) 
{
    
    
    IOHIDDeviceInterface**  deviceInterface = (IOHIDDeviceInterface**)target;
    
    Drumset*                drums = (Drumset*)refcon;
    IOHIDQueueInterface**   queue = [drums getQueue];
    //IOHIDQueueInterface**  queue = (IOHIDQueueInterface**)sender;
    cookie_struct_t         cookies = [drums getHidCookies];
    
    AbsoluteTime            zeroTime    = {0,0};
    IOHIDEventStruct        event;
    
    // can't really do anything without a device or cookie data
    if ( !deviceInterface || !cookies)
        return;
    
    // Iterate through all the queue events that were recorded since the last 
    // callback invocation.  (Multiple events might've been added to the queue)
    while (result == kIOReturnSuccess) {
        
        result = (*queue)->getNextEvent(queue,&event,zeroTime,0);
        
        if ( result != kIOReturnSuccess )
            continue;
        
        // Only intersted in short values right now
        if ((event.longValueSize != 0) && (event.longValue != NULL)) {
            free(event.longValue);
            continue;
        }
        
        [drums processDrumState:(event.value == 1) forHIDCookie:event.elementCookie];
        
        //update the UI
        [[drums listenerObj] performSelector:[drums listenerSel]];
        printf(" --- %d = %d --- \n", event.elementCookie, event.value);
        
    }
    
    result = kIOReturnSuccess;
}
                
                






@implementation Drumset

/**
 * Static method that finds a drumset in the IORegistry and returns the 
 * IOHIDDeviceInterface pointing to it.
 */
+(IOHIDDeviceInterface**)findHIDDrumset  {
    mach_port_t             masterPort = NULL;
    io_iterator_t           hidObjectIterator = NULL;
    IOReturn                ioReturnValue = kIOReturnSuccess;
    IOHIDDeviceInterface ** pphidDeviceInterface = NULL;
    short                   numDevice = 0;
    
    ioReturnValue = IOMasterPort(bootstrap_port, &masterPort);
    
    /////// -- Fetch HID Devices (generic desktop gamepads)
    UInt32 usagePage = kHIDPage_GenericDesktop;
    UInt32 usage     = kHIDUsage_GD_GamePad;
    CFNumberRef refUsage = NULL, refUsagePage = NULL;
    CFMutableDictionaryRef hidMatchDictionary = NULL;
    
    // Set up matching dictionary to search the I/O Registry for HID devices we 
    // are interested in. Dictionary reference is NULL if error.
    hidMatchDictionary = IOServiceMatching (kIOHIDDeviceKey);
    if (hidMatchDictionary != NULL) {
        // Add key for device type to refine the matching dictionary.
        refUsagePage = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usagePage);
        refUsage = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &usage);
        CFDictionarySetValue(hidMatchDictionary, 
                             CFSTR(kIOHIDPrimaryUsagePageKey),
                             refUsagePage);
        CFDictionarySetValue(hidMatchDictionary, 
                             CFSTR(kIOHIDPrimaryUsageKey),
                             refUsage);
    }
    else
        printf("Couldn't create a matching dictionary.\n");
    
    // Now search I/O Registry for matching devices.
    ioReturnValue = IOServiceGetMatchingServices(masterPort, 
                                                 hidMatchDictionary, 
                                                 &hidObjectIterator);
    // If error, print message and hang (for debugging purposes).
    if ((ioReturnValue != kIOReturnSuccess) | (hidObjectIterator == 0))
        printf("Couldn't create a HID object iterator.\n");
    
    // IOServiceGetMatchingServices consumes a reference to the dictionary, so we 
    // don't need to release the dictionary ref.
    hidMatchDictionary = NULL;  
    
    /////// -- Iterate through the devices, find a compatible drumset
    
    fprintf(stderr, "HID Object Iterator: %x\n", hidObjectIterator);
    
    if(hidObjectIterator != 0) {
        //print 'em out.
        io_object_t hidDevice = NULL;
        
        while ((hidDevice = IOIteratorNext(hidObjectIterator))) {
            printf ("\n--- Device %d ---\n", numDevice++);
            //pphidDeviceInterface = MyCreateHIDDeviceInterface (hidDevice);
            io_name_t className;
            IOCFPlugInInterface **plugInInterface = NULL;
            HRESULT plugInResult = S_OK;
            SInt32 score = 0;
            
            ioReturnValue = IOObjectGetClass (hidDevice, className);
            if ( ioReturnValue != kIOReturnSuccess )
                printf("Failed to get class name.\n");
            
            printf ("Creating interface for device of class %s\n\n", className);
            
            ioReturnValue = IOCreatePlugInInterfaceForService(
                                hidDevice, kIOHIDDeviceUserClientTypeID, 
                                kIOCFPlugInInterfaceID, 
                                &plugInInterface, &score
                            );
            
            if (ioReturnValue == kIOReturnSuccess) {
                // Call a method of the intermediate plug-in to create the device interface
                plugInResult = (*plugInInterface)->QueryInterface(plugInInterface,
                                                                  CFUUIDGetUUIDBytes(kIOHIDDeviceInterfaceID), 
                                                                  (void **) &pphidDeviceInterface);
                if (plugInResult != S_OK)
                    printf ("Couldn't query HID class device interface from plugInInterface\n");
                
                (*plugInInterface)->Release(plugInInterface);
            }
            else
                printf ("Failed to create **plugInInterface via IOCreatePlugInInterfaceForService.\n");
            
            
            // Release the device (we only need the interface
            ioReturnValue = IOObjectRelease(hidDevice);
            
            if ( ioReturnValue != kIOReturnSuccess )
                printf("Error releasing HID device\n");
            
            if (*pphidDeviceInterface != NULL) {
                // open the device
                ioReturnValue = (*pphidDeviceInterface)->open(pphidDeviceInterface, 0);
                printf ("Open result = %d\n", ioReturnValue);

                if (ioReturnValue != KERN_SUCCESS) {
                    printf("Couldn't open the drum device interface.\n");
                }
                
                // NEXT: find out if it is a drumset
                CFTypeRef                 object;
                CFMutableDictionaryRef        properties = 0;
                
                IORegistryEntryCreateCFProperties(hidDevice, &properties,
                                                  kCFAllocatorDefault, kNilOptions);
                
                //Get vendor ID
                long vendorID;
                object = CFDictionaryGetValue(properties, CFSTR(kIOHIDVendorIDKey));
                if(object == 0 || CFGetTypeID(object) != CFNumberGetTypeID())
                    continue; //check next in iterator
                if (!CFNumberGetValue((CFNumberRef) object, kCFNumberLongType, &vendorID))
                    continue; //check next in iterator
                
                printf("Passed vendor ID type check, vendor = %x\n", vendorID);
                
                //Get product ID
                long productID;
                object = CFDictionaryGetValue(properties, CFSTR(kIOHIDProductIDKey));
                if (object == 0 || CFGetTypeID(object) != CFNumberGetTypeID())
                    continue; //check next in iterator
                if (!CFNumberGetValue((CFNumberRef) object, kCFNumberLongType, &productID))
                    continue; //check next in iterator
                
                printf("Passed product ID type check, product = %x\n", productID);
                
                if(productID == kOurProductID && vendorID == kOurVendorID) {
                    printf("Found Drums!\n\n");
                    break; //out to return (found a drumset!)
                }
            }
        }          
        IOObjectRelease(hidObjectIterator);
    }
    
    //Free master port if we created one.
    if (masterPort)
        mach_port_deallocate (mach_task_self (), masterPort);    
    fflush(stdout);
    
    // either a valid device interface, or could be NIL
    return pphidDeviceInterface;
}

-(void)setListener:(id)obj selector:(SEL)s {
    self->listenerObj = obj;
    self->listenerSel = s;
}


-(id)init {
    [super init];
    
    self->redDrumMidiNote    = kRedDrumNoteDefault;
    self->yellowDrumMidiNote = kYellowDrumNoteDefault;
    self->blueDrumMidiNote   = kBlueDrumNoteDefault;
    self->greenDrumMidiNote  = kGreenDrumNoteDefault;
    self->pedalMidiNote      = kPedalNoteDefault;
    self->midiChannel        = kMidiChannelDefault;
    
    self->usingSoftwareSynth = YES;
    
    return self;
}

-(id)initWithDeviceInterface:(IOHIDDeviceInterface **)deviceInterface {
    self = [self init];
    self->cookies = getHIDCookies((IOHIDDeviceInterface122 **)deviceInterface);
    [self setupQueueCallbackOnInterface:deviceInterface];
    [self setupSoftwareSynth];
    return self;
}

/**
 * Disconnects all the pieces of the software synth module.
 */
-(void)teardownSoftwareSynth {
    AUGraphStop (self->outGraph); 
    DisposeAUGraph (self->outGraph);
}

/**
 * Connects all the bits of the software synth and MIDI system to produce
 * audio output when the drum controller is hit.
 */
-(void)setupSoftwareSynth {
    
    OSStatus result;
    AUNode synthNode, limiterNode, outNode;
    self->outGraph = 0;
    //self->outSynth; // the synth unit

    // Set up audio units and get ready to synthesize software
    // This call creates the Graph and the Synth unit...
    ComponentDescription cd;
    cd.componentManufacturer = kAudioUnitManufacturer_Apple;
    cd.componentFlags = 0;
    cd.componentFlagsMask = 0;
    
    require_noerr(result = NewAUGraph (&(self->outGraph)), failure);
    printf(" ... Created new AU Graph\n");
    
    cd.componentType = kAudioUnitType_MusicDevice;
    cd.componentSubType = kAudioUnitSubType_DLSSynth;
    
    require_noerr(result = AUGraphAddNode (self->outGraph, &cd, &synthNode), failure);
    printf(" ... Created new synth node on graph\n");
    
    cd.componentType = kAudioUnitType_Effect;
    cd.componentSubType = kAudioUnitSubType_PeakLimiter;  
    
    require_noerr(result = AUGraphAddNode (self->outGraph, &cd, &limiterNode), failure);
    printf(" ... Created new peak limiter node on graph\n");
    
    cd.componentType = kAudioUnitType_Output;
    cd.componentSubType = kAudioUnitSubType_DefaultOutput;  
    require_noerr(result = AUGraphAddNode (self->outGraph, &cd, &outNode), failure);
    printf(" ... Created new output node on graph\n");
    
    require_noerr(result = AUGraphOpen (self->outGraph), failure);
    printf(" ... opened graph\n");
    
    require_noerr(result = AUGraphConnectNodeInput(self->outGraph, synthNode, 0, limiterNode, 0), failure);
    require_noerr(result = AUGraphConnectNodeInput(self->outGraph, limiterNode, 0, outNode, 0), failure);
    printf(" ... connected graph nodes\n");
    
    // ok we're good to go - get the Synth Unit...
    require_noerr(result = AUGraphNodeInfo(self->outGraph, synthNode, 0, &(self->outSynth)), failure);
    printf(" ... fetched audio unit \n");
    
    require_noerr(result = AUGraphInitialize(self->outGraph), failure);
    printf(" ... initialized AUGraph\n");
    
    
    // now start up the connections
    require_noerr(result = MusicDeviceMIDIEvent(self->outSynth, 
                                                kMidiMessage_ControlChange << 4 | self->midiChannel, 
                                                kMidiMessage_BankMSBControl, 0,
                                                0/*sample offset*/), failure);
    printf(" ... midi bank set up\n");
    
    require_noerr(result = MusicDeviceMIDIEvent(self->outSynth, 
                                                kMidiMessage_ProgramChange << 4 | self->midiChannel, 
                                                kDrumVoiceProgram/*prog change num*/, 0,
                                                0/*sample offset*/), failure);
    printf(" ... midi program set up\n");
    
    require_noerr(result = AUGraphStart(self->outGraph), failure);
    printf(" ... started AUGraph up\n");
    return;
failure:
    printf("Some sort of error occurred %lx\n", result);
}

/**
 * Essentially picks which drum hit to process based on cookie value and drum state.
 */
-(void)processDrumState:(BOOL)isOn forHIDCookie:(IOHIDElementCookie)cookieValue {
    if(cookieValue == cookies->gRedDrumCookie) {
        if(isOn)
            [self processDrumHit:RED_DRUM];
        [self setRed_drum_value:(isOn ? 127 : 0)];
    }
    else if(cookieValue == cookies->gYellowDrumCookie) {
        if(isOn)
            [self processDrumHit:YELLOW_DRUM];
        [self setYellow_drum_value:(isOn ? 127 : 0)];
    }
    else if(cookieValue == cookies->gBlueDrumCookie) {
        if(isOn)
            [self processDrumHit:BLUE_DRUM];
        [self setBlue_drum_value:(isOn ? 127 : 0)];
    }
    else if(cookieValue == cookies->gGreenDrumCookie) {
        if(isOn)
            [self processDrumHit:GREEN_DRUM];
        [self setGreen_drum_value:(isOn ? 127 : 0)];
    }
    else if(cookieValue == cookies->gPedalCookie) {
        if(isOn)
            [self processDrumHit:PEDAL_DRUM];
        [self setPedal_drum_value:(isOn ? 127 : 0)];
    }
}


/**
 * This processes a drum hit, and translates it into an action (whether the
 * action is a software synth note or a MIDI system note).
 */
-(void)processDrumHit:(drum_index_t)index {    
    if(index & RED_DRUM) {
        [self sendMidiHit:self->redDrumMidiNote onChannel:self->midiChannel];
    }
    if(index & YELLOW_DRUM) {
        [self sendMidiHit:self->yellowDrumMidiNote onChannel:self->midiChannel];
    }
    if(index & BLUE_DRUM) {
        [self sendMidiHit:self->blueDrumMidiNote onChannel:self->midiChannel];
    }
    if(index & GREEN_DRUM) {
        [self sendMidiHit:self->greenDrumMidiNote onChannel:self->midiChannel];
    }
    if(index & PEDAL_DRUM) {
        [self sendMidiHit:self->pedalMidiNote onChannel:self->midiChannel];
    }
}

/**
 * This sends a software synth note or a MIDI system note given a channel.
 */
-(void)sendMidiHit:(int)note onChannel:(int)channel {
    if(self->usingSoftwareSynth) {
        OSStatus result;
        UInt32 noteNum = note; // + 30;
        UInt32 onVelocity = 127;
        UInt32 noteOnCommand =     kMidiMessage_NoteOn << 4 | self->midiChannel;
                
        result = MusicDeviceMIDIEvent(self->outSynth, noteOnCommand, noteNum, onVelocity, 0);
        if(result) { printf("Error sending note on command\n"); }
        result = MusicDeviceMIDIEvent(self->outSynth, noteOnCommand, noteNum, 0, 0);
        if(result) { printf("Error sending note off command\n"); }
    }    
}

-(IOHIDQueueInterface**) getQueue {
    return self->hidQueue;
}

-(cookie_struct_t) getHidCookies {
    return self->cookies;
}

/**
 * Given an IOHIDDeviceInterface**, sets up the queue and callback so 
 * this instance can get drum hit events.
 */
-(void)setupQueueCallbackOnInterface:(IOHIDDeviceInterface **)deviceInterface {
    HRESULT result;
    CFRunLoopSourceRef tCFRunLoopSourceRef = NULL;
    
    self->hidQueue = (*deviceInterface)->allocQueue(deviceInterface);
    IOHIDQueueInterface** q = self->hidQueue;

    printf("Queue allocated: %lx\n", (long) self->hidQueue);
    if (self->hidQueue) {
        //create the queue
        result = (*q)->create(q, 0, 8);
        printf ("Queue create result: %lx\n", result);
        
        
        result = (*q)->createAsyncEventSource(q, &tCFRunLoopSourceRef);
        if (kIOReturnSuccess != result)
            printf ("Failed to createAsyncEventSource, error: %ld.\n", result);
        
        // if we have one now
        if (NULL != tCFRunLoopSourceRef)
        {
            CFRunLoopRef tCFRunLoopRef = (CFRunLoopRef) GetCFRunLoopFromEventLoop(GetMainEventLoop());
            
            // and it's not already attached to our runloop\xC9
            if (!CFRunLoopContainsSource(tCFRunLoopRef, tCFRunLoopSourceRef, kCFRunLoopDefaultMode))
                // then attach it now.
                CFRunLoopAddSource(tCFRunLoopRef, tCFRunLoopSourceRef, kCFRunLoopDefaultMode);
        }
        
        // now install our callback
        result = (*q)->setEventCallout(q, DrumInternalIOHIDCallbackFunction, deviceInterface, self);
        if (kIOReturnSuccess != result)
            printf ("Failed to setEventCallout, error: %ld.\n", result);
        
        //add elements to the queue (this primes it)
        result = (*q)->addElement(q, cookies->gXAxisCookie, 0);
        result = (*q)->addElement(q, cookies->gRedDrumCookie, 0);
        result = (*q)->addElement(q, cookies->gYellowDrumCookie, 0);
        result = (*q)->addElement(q, cookies->gBlueDrumCookie, 0);
        result = (*q)->addElement(q, cookies->gGreenDrumCookie, 0);
        result = (*q)->addElement(q, cookies->gPedalCookie, 0);
        
        //DON't remove elements, it will "unprime" the queue.        
        
        //start data delivery to queue
        result = (*q)->start(q);
        printf ("queue start result: %lx\n", result);
        fflush(stdout);
    }
}

/**
 * This method is for testing the queue and callback.  It creates a loop until
 * the mouse is clicked, and events just get processed.  Any errors are
 * displayed in stdout.
 */
-(void)testQueueCallback {
    HRESULT result;
    IOHIDEventStruct event;
    
    //check queue a few times to see accumulated events
    printf ("Testing Event Interface (Mouse button to continue)...\n");
    fflush (stdout);
    while (Button())  {}
    while (!Button()) {
        AbsoluteTime zeroTime = {0,0};
        result = (*self->hidQueue)->getNextEvent (self->hidQueue, &event, zeroTime, 0);
        if (result == kIOReturnUnderrun) {
            ;// print nothing queue empty
        }
        else if (result != kIOReturnSuccess)
            printf ("queue getNextEvent result error: %lx\n", result);
        else
            printf ("queue: event:[%lx] %ld\n", 
                    (unsigned long) event.elementCookie, event.value);
        fflush (stdout);
        CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.25f, true);
    }
    fflush (stdout);
}

/**
 * Disconnects the queue and callback, cleaning up.
 */
-(void)teardownQueueCallback {
    HRESULT result;
    CFRunLoopSourceRef tCFRunLoopSourceRef = NULL;
    IOHIDQueueInterface** q = self->hidQueue;

    //stop data delivery to queue
    result = (*q)->stop(q);
    printf ("queue stop result: %lx\n", result);
    
    // see if we have an Async event source for this queue, and if we do
    // and it is attached to the main loop, disconnect it.
    tCFRunLoopSourceRef = (*q)->getAsyncEventSource(q);
    if (NULL != tCFRunLoopSourceRef) {
        CFRunLoopRef tCFRunLoopRef = (CFRunLoopRef) GetCFRunLoopFromEventLoop(GetMainEventLoop());
        
        // if it's attached to our runloop\xC9
        if (CFRunLoopContainsSource(tCFRunLoopRef, tCFRunLoopSourceRef, kCFRunLoopDefaultMode))
            // then remove it
            CFRunLoopRemoveSource(tCFRunLoopRef, tCFRunLoopSourceRef, kCFRunLoopDefaultMode);
        // now release it.
        CFRelease(tCFRunLoopSourceRef);
    } else {
        printf("No async event source!\n");
    }
    
    //dispose of queue
    result = (*q)->dispose(q);
    
    //release the queue we allocated
    result = (*q)->Release(q);
    printf ("queue release result: %lx\n", result);
    
    //take down audio system
    [self teardownSoftwareSynth];
    
    fflush (stdout);    
}

-(int) getMidiNote:(drum_index_t)drum 
{
    switch (drum) {
        case RED_DRUM:
            return redDrumMidiNote;
        case YELLOW_DRUM:
            return yellowDrumMidiNote;
        case BLUE_DRUM:
            return blueDrumMidiNote;
        case GREEN_DRUM:
            return greenDrumMidiNote;
        case PEDAL_DRUM:
            return pedalMidiNote;
    }
    return 0;
}

-(void) setMidiNote:(int)note forDrum:(drum_index_t)drum
{
    switch (drum) {
        case RED_DRUM:
            redDrumMidiNote = note;
            break;
        case YELLOW_DRUM:
            yellowDrumMidiNote = note;
            break;
        case BLUE_DRUM:
            blueDrumMidiNote = note;
            break;
        case GREEN_DRUM:
            greenDrumMidiNote = note;
            break;
        case PEDAL_DRUM:
            pedalMidiNote = note;
            break;
    }
}
    
// defines getter and setter implementations
@synthesize yellow_drum_value,
            red_drum_value, 
            blue_drum_value, 
            green_drum_value, 
            pedal_drum_value,
listenerObj,
listenerSel;

@end
